package com.cq.hd.app.biz;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cq.hd.app.config.WxPubConfig;
import com.cq.hd.common.constant.RedisKeyConstant;
import com.cq.hd.common.utils.RedisUtil;
import com.cq.hd.common.utils.SHA1;
import com.cq.hd.common.utils.aes.AesException;
import com.cq.hd.common.utils.wxpub.MessageUtil;
import com.cq.hd.common.utils.wxpub.WXBizMsgCrypt;
import com.cq.hd.member.api.TbAppUserApi;
import com.cq.hd.member.api.TbWxSubscribeRecordApi;
import com.cq.hd.member.api.dto.WxSubscribeRecordSaveDto;
import com.cq.hd.member.api.vo.AppUserVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Map;

@Component
@Slf4j
public class WxOfficialBiz {

    @Autowired
    private WxPubConfig wxPubConfig;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private TbWxSubscribeRecordApi wxSubscribeRecordApi;

    @Autowired
    private TbAppUserApi appUserApi;

    public void checkForGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        log.info("checkForGet====");

        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");
        log.info("-----------------------开始校验------------------------");

        log.info("signature:{}", signature);
        log.info("timestamp:{}",  timestamp);
        log.info("nonce:{}", nonce);
        log.info("echostr:{}", echostr);

        //排序
        String sortString = sort(wxPubConfig.token, timestamp, nonce);
        //加密
        String myString = SHA1.sha1(sortString);
        //校验
        if (!StringUtils.isEmpty(myString) && myString.equals(signature)) {
            log.info("签名校验通过");
            // 如果检验成功输出echostr，微信服务器接收到此输出，才会确认检验完成。
            response.getWriter().write(echostr);
        } else {
            log.error("签名校验失败");
            log.error("token:{}", wxPubConfig.token);
            log.error("sortString:{}", sortString);
            log.error("myString:{}", myString);
        }
    }

    public void checkForPost(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        log.info("checkForPost====");

        // 将请求、响应的编码均设置为UTF-8（防止中文乱码）
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        String encryptType = request.getParameter("encrypt_type");
        // 微信加密签名
        String signature = request.getParameter("signature");
        // 时间戳
        String timestamp = request.getParameter("timestamp");
        // 随机数
        String nonce = request.getParameter("nonce");

        log.info("encryptType:" + encryptType);
        log.info("signature:" + signature);
        log.info("timestamp:" + timestamp);
        log.info("nonce:" + nonce);

        // 响应消息
        try {
            PrintWriter out = response.getWriter();
            //排序
            String sortString = sort(wxPubConfig.token, timestamp, nonce);
            //加密
            String myString = SHA1.sha1(sortString);
            //校验
            if (!StringUtils.isEmpty(myString) && myString.equals(signature)) {
                Map<String, String> requestMap;
                String respXml;
                if ("aes".equals(encryptType)) {
                    WXBizMsgCrypt crypt = getWxCrypt();
                    requestMap = MessageUtil.parseXmlCrypt(request, crypt);
                    respXml = processRequest(requestMap, timestamp);
                    respXml = crypt.encryptMsg(respXml, timestamp, nonce);
                } else {
                    requestMap = MessageUtil.parseXml(request);
                    respXml = processRequest(requestMap, timestamp);
                }
                log.info(respXml);
                respXml = respXml.replace(" ", "");
                out.print(respXml);
                out.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理用户发给公众号的消息
     */
    public String processRequest(Map<String, String> requestMap, String timestamp) {
        log.info("接收微信公众号消息:" + JSON.toJSONString(requestMap));

        // xml格式的消息数据
        String respXml = "";
        try {
            // 发送方帐号
            String fromUserName = requestMap.get("FromUserName");
            // 开发者微信号
            String toUserName = requestMap.get("ToUserName");
            // 消息类型
            String msgType = requestMap.get("MsgType");
            // 事件类型
            String event = requestMap.get("Event");

            if (!"event".equals(msgType)) {
                return respXml;
            }

            // 订阅
            if ("subscribe".equals(event)) {
                // 根据openid调用接口获取unionid
                String getWxUserInfoResult = wxPubConfig.getWxUserInfo(fromUserName);
                if (StringUtils.isEmpty(getWxUserInfoResult)) {
                    return respXml;
                }

                JSONObject jsonObject = JSONObject.parseObject(getWxUserInfoResult);
                String unionid = jsonObject.getString("unionid");
                if (StringUtils.isEmpty(unionid)) {
                    log.error("用户:【{}】关注公众号失败, 根据openid获取unionid为空，result：{}，event:【{}】", fromUserName, getWxUserInfoResult, event);
                    return respXml;
                }

                // 根据unionid查询用户表获取用户数据
                AppUserVo appUserVo = appUserApi.getByUnionid(unionid).unpack();
                if (appUserVo == null) {
                    log.error("用户:【{}】关注公众号失败, 根据unionid获取用户数据为空，result：{}，event:【{}】", fromUserName, unionid, event);
                    return respXml;
                }
                Long userId = appUserVo.getId();

                log.info("用户:【{}】关注公众号成功, event:【{}】, userId:【{}】", fromUserName, event, userId);
                Object subscribeCache = redisUtil.hGet(RedisKeyConstant.WX_PUB_SUBSCRIBE_KEY, String.valueOf(userId));
                if (subscribeCache != null) {
                    return respXml;
                }

                WxSubscribeRecordSaveDto wxSubscribeRecordSaveDto = new WxSubscribeRecordSaveDto();
                wxSubscribeRecordSaveDto.setUserId(userId);
                wxSubscribeRecordSaveDto.setOpenid(fromUserName);
                wxSubscribeRecordSaveDto.setSubscribeTime(timestamp);
                if (!wxSubscribeRecordApi.saveWxSubscribeRecord(wxSubscribeRecordSaveDto).unpack()) {
                    log.error("用户:【{}】关注公众号失败, 添加关注记录失败，event:【{}】，userId:【{}】", fromUserName, event, userId);
                }

                return respXml;
            }

            // 取消订阅
            if ("unsubscribe".equals(event)) {
                log.info("用户:【{}】取关公众号, event:【{}】", fromUserName, event);

                // 删除表记录，并删除缓存
                if (!StringUtils.isEmpty(fromUserName)) {
                    wxSubscribeRecordApi.delByOpenId(fromUserName);
                }

                return respXml;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return respXml;
    }

    public WXBizMsgCrypt getWxCrypt() {
        WXBizMsgCrypt crypt = null;
        try {
            crypt = new WXBizMsgCrypt(wxPubConfig.token, wxPubConfig.encodingAesKey, wxPubConfig.appId);
        } catch (AesException e) {
            e.printStackTrace();
        }
        return crypt;
    }

    public static String sort(String token, String timestamp, String nonce) {
        String[] strArray = {token, timestamp, nonce};
        Arrays.sort(strArray);
        StringBuilder sb = new StringBuilder();
        for (String str : strArray) {
            sb.append(str);
        }

        return sb.toString();
    }
}
